/*
\begin{listingcont}
*/
#include "scheduler.h"

listRingContext chedRing;

/**
* Daten von Thread in der Ringliste 
* speichern
*/
int insert(chedContext* element)
{
	if(!element)
		return 0;

	// check ob, die liste leer ist
	// Wenn ja, dann head = element
	if(chedRing.length == 0)
	{
		chedRing.head = element;
		chedRing.head->next = chedRing.head;
		chedRing.head->prev = chedRing.head;
		chedRing.length++;
		return 1;
	}
	// Element in die Liste einfuegen
	element->next = chedRing.head->next;
	chedRing.head->next->prev = element;
	chedRing.head->next = element;
	element->prev = chedRing.head;
	chedRing.length++;
		
	return 1;
}

/**
* Registriert ein neues Thread beim Scheduler.
* 
* Die daten diesem Thread wird in der RingListe gespeichert
*/
int schedRegisterUserThread(schedFuncPtr_t func,
                            void *args,
                            unsigned int argsize,
                            char *name,
                            unsigned int prio)
{

	void *stackPtr;
	// initialise element of chedContext
	chedContext *uctx = (chedContext*)malloc(sizeof(chedContext));
	if(!uctx)
		return -2; // Fehler beim Malloc

	// Error Wenn Fehlende Parameter
	if(!args || !name || !func)
		return -1;

	// Parameter Initialisierung
	uctx->func = func;
	uctx->args = args;
	uctx->name = name;
	uctx->prio = prio;
	uctx->runde= prio;
    	uctx->running = 1; // Kann laufen
	uctx->argsize = argsize;

	stackPtr = calloc(1, argsize);

	// Fuegt der Kontext im Ringlist
	insert(uctx);

#if defined(__linux__) && defined(__i386__)
    uctx->env[0].__jmpbuf[5] = (int)schedWrapper; /* EIP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[5]);
    uctx->env[0].__jmpbuf[3] = 0; /* EBP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[3]);
    uctx->env[0].__jmpbuf[4] = (int)stackPtr + argsize - sizeof(int32_t); /* ESP */
    *((void**)uctx->env[0].__jmpbuf[4]) = (void *)0; /* old EIP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[4]);
#elif defined(__linux__) && defined(__x86_64__)
    uctx->env[0].__jmpbuf[7] = (long int)schedWrapper; /* RIP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[7]);
    uctx->env[0].__jmpbuf[1] = 0; /* RBP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[1]);
    uctx->env[0].__jmpbuf[6] = (long int)stackPtr + argsize - sizeof(int64_t); /* RSP */
    *((void**)uctx->env[0].__jmpbuf[6]) = (void *)0; /* old RIP */
    PTR_MANGLE(uctx->env[0].__jmpbuf[6]);
#elif defined(__linux__) && defined(__PPC__)
    uctx->env[0].__jmpbuf[2] = (int)schedWrapper; /* LR */
    uctx->env[0].__jmpbuf[0] = (int)stackPtr + argsize - 32 * sizeof(int); /* SP */
    *((int *)uctx->env[0].__jmpbuf[0]) = 0; /* old SP */
    *((int *)uctx->env[0].__jmpbuf[0] + sizeof(int)) = 0; /* old LR */
    PTR_MANGLE(uctx->env[0].__jmpbuf[0]);
    PTR_MANGLE(uctx->env[0].__jmpbuf[2]);
#elif defined(__MACH__) && defined(__ppc__)   /* ppc32-osx */
    uctx->env[0] = ((int)stackPtr + argsize - 32 * sizeof(int)); /* SP */
    *((int *)uctx->env[0]) = 0; /* old SP */
    *((int *)uctx->env[0] + sizeof(int)) = 0; /* old LR */
    uctx->env[21] = (int)schedWrapper; /* LR */
#elif defined(__MACH__) && defined(__i386__)   /* i386-osx */
    uctx->env[12] = (int)schedWrapper; /* EIP */
    uctx->env[8] = 0; /* EBP */
    uctx->env[9] = (int)stackPtr + argsize - sizeof(int32_t); /* ESP */
    *((void**)uctx->env[9]) = (void *)0; /* old EIP */
#endif 

	return 0;
}

/**
* Unregistriert ein Thread beim Scheduler
*
*@param name Name des Thread zu loeschen
*/
void schedUnregisterUserThread(char *name)
{
	// Es gibt keine Thread ohne Name
	if(!name)
		return;
	
	chedContext* current = chedRing.head;
	// flag find, wird gesetzt wenn der Thread gefunden wird
	int find = 0;
	// i ist der Iterator fuer die for schleife	
	int i = 0;
	// Suchen der Thread mit der Name name im Ringlist
	for(; !find && i < chedRing.length; i++, current = current->next)
	{
		if(!strcmp(current->name, name))
		{
			find = 1;
			break;
		}
	}

	// Wenn nicht gefunden, dann nix machen
	if(!find)
		return;
	
	// Verschiebe der Zeigern chedRing.curr bzw chedRing.head
	// wenn der gleich der zuloeschende Element ist.
	if(chedRing.curr == current)
		chedRing.curr = current->prev;
	
	if(current == chedRing.head)
		chedRing.head = chedRing.head->prev;

	// Element loeschen
	current->next->prev = current->prev;
	current->prev->next = current->next;
	current->next = 0;
	current->prev = 0;

	// clear
	free(current);
	// Laenge des Ring dekrementieren
	chedRing.length--;
	return;
}

/**
* Activierung den Threads
*/	
void schedActivateUserThread()
{
	// Solange Thread noch da sind
	while(chedRing.length > 0)
	{
		int iAct = 0;
		int size = chedRing.length;
		
		chedRing.curr = chedRing.head;
		// Eine ganze durchlauf is eine Runde
		printf("---neue Runde---\n");
		for(iAct = 0; iAct < size; iAct++)
		{
			// Wenn der Aktuell Thread is am laufen
			// und seine Prioritaet kleiner gleich die Runde ist
			// dann wird er aktiviert
			int p = chedRing.curr->runde;
		    	if( p == 0 && chedRing.curr->running )
			{
				chedRing.curr->runde = chedRing.curr->prio; 
				//schedule next thread
				if ( !setjmp(chedRing.env)) 
				{
					printf("ched: thread %s\n", chedRing.curr->name);
					longjmp(chedRing.curr->env,1);
				}
			}
			else
				chedRing.curr->runde--;
	
			if(chedRing.curr)
				chedRing.curr = chedRing.curr->next; // Next
		}
		printf("----------------\n\n");
	}
}

/**
* freiewillige Kontext wechsel
*/
void schedYield()
{
	if ( !setjmp(chedRing.curr->env) ) 
		longjmp(chedRing.env, 1);
}

/** 
*wrapper for starting user space thread
*/
void schedWrapper()
{
	// get context for thread
	chedContext *c = chedRing.curr;
	
	// start function
	chedRing.curr->func(chedRing.curr->args);
	
	// if the function has terminated, we are at this point 
	// set flag that indicates that function has terminated
	chedRing.curr->running = 0;
	
	// function has finished, continue with the scheduler
	if ( !setjmp(chedRing.curr->env) ) 
	{
		schedUnregisterUserThread(chedRing.curr->name); // Thread entfernen
		longjmp(chedRing.env, 1);		//Zurueck zu scheduler
	}
}

/**
* Loescht die RingListe
*/
void chedClean()
{
	// Alle Thread unregistrieren
	while(chedRing.length > 0)
		schedUnregisterUserThread(chedRing.head->name);
		
}
/*
\end{listingcont}
*/

